package com.exam.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {

    private final ObjectMapper objectMapper;

    /**
     * 切面表达式，用于指定要拦截的方法
     *
     * @Pointcut("execution(public * com.exam.controller.*.*(..))")
     * // 定义一个切面，该切面用于拦截 com.exam.controller 包下所有类的所有公共方法
     * public void logPoint() {
     * }
     */
    @Pointcut("execution(public * com.exam.controller.*.*(..))")
    public void logPoint() {
    }

    /**
     * 使用环绕通知拦截目标方法，记录请求和响应信息，并返回目标方法的执行结果
     *
     * @param point 切面连接点对象
     * @return 目标方法的执行结果
     * @throws Throwable 如果在环绕处理过程中发生异常，则抛出该异常
     */
    @Around("logPoint()")
    public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        // 获取请求信息
        // request info
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        log.info("[Request] {} url: {} headers: {}, execute method: {}#{}",
                request.getMethod(),
                String.format("%s?%s", request.getServletPath(), Optional.ofNullable(request.getQueryString()).orElse("")),
                getRequestHeaders(request),
                point.getSignature().getDeclaringTypeName(),
                point.getSignature().getName()
        );

        // 记录开始时间
        Long start = System.currentTimeMillis();

        // 执行目标方法
        // execute target method
        Object result = point.proceed();

        // 记录结束时间
        Long end = System.currentTimeMillis();

        // 获取响应信息
        // response info
        HttpServletResponse response = attributes.getResponse();
        log.info("[Response] {} {}ms, headers: {}, body: {}",
                Objects.requireNonNull(response).getStatus(),
                end - start,
                getResponseHeaders(response),
                objectMapper.writeValueAsString(result)
        );

        // 返回目标方法的执行结果
        return result;
    }

    /**
     * 获取请求头信息
     *
     * @param request HttpServletRequest对象
     * @return 包含请求头信息的Map对象
     */
    private Map<String, String> getRequestHeaders(HttpServletRequest request) {
        // 创建一个用于存储请求头信息的Map对象
        Map<String, String> headers = new HashMap<>();
        // 获取请求头名称的枚举集合
        Enumeration<String> headerNames = request.getHeaderNames();
        // 遍历请求头名称的枚举集合
        while (headerNames.hasMoreElements()) {
            // 获取当前请求头名称
            String key = headerNames.nextElement();
            // 获取当前请求头名称对应的值
            String value = request.getHeader(key);
            // 将请求头名称和值存储到Map对象中
            headers.put(key, value);
        }
        // 返回包含请求头信息的Map对象
        return headers;
    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
    /**
     * 获取响应头信息
     *
     * @param response HttpServletResponse对象
     * @return 包含响应头信息的Map对象
     */
    private Map<String, String> getResponseHeaders(HttpServletResponse response) {
        // 创建一个HashMap对象，用于存储响应头信息
        HashMap<String, String> headers = new HashMap<>();
        // 遍历响应头名称
        for (String headerName : response.getHeaderNames()) {
            // 将响应头名称和对应的值存入HashMap中
            headers.put(headerName, response.getHeader(headerName));
        }
        // 返回包含响应头信息的Map对象
        return headers;
    }

}
